using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Lync.Widget.Library.Presence;
using Lync.Widget.Library.ServiceHelpers.ServiceHelpers;
using log4net;

namespace Lync.Widget.Library.InstantMessaging
{
    public class UCInstantGroupMessageRouter : UCInstantMessageRouterBase
    {
        #region Private Fields

        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
        private static UCInstantGroupMessageRouter m_Instance;
        private readonly List<UCInstantGroupMessageProvider> m_Providers = new List<UCInstantGroupMessageProvider>();
        private readonly List<GroupMessageQueue> m_MessageQueues = new List<GroupMessageQueue>();
        #endregion

        #region Public Properties

        public static UCInstantGroupMessageRouter Instance
        {
            get { return m_Instance ?? (m_Instance = new UCInstantGroupMessageRouter()); }
        }

        #endregion


        #region Public Methods

        public void InitChatSession(GroupSubscription groupSubscription, string session, string webUserName, string page, string title, string topic, bool isInInitialMode)
        {
            if (!QueueExists(session))
            {
                var queue = new GroupMessageQueue(session);
                m_MessageQueues.Add(queue);
            }

            try
            {
                var huntgroupInfo = groupSubscription.Presences
                                              .OrderBy(p => p.Priority)
                                              .Select(p => new { p.SipUri, p.Priority })
                                              .ToList();

                foreach (var info in huntgroupInfo)
                {
                    //check if session provider does not exist yet.
                    if (SessionProviderExists(session, info.SipUri))
                    {
                        break;
                    }

                    //create provider for the session
                    var provider = new UCInstantGroupMessageProvider(CollabPlatform, session, info.SipUri, webUserName, page, title, topic, info.Priority);
                    provider.ChatRequestAcceptanceChanged += ProviderChatRequestAcceptanceChanged;
                    provider.ChatMessageReceivedFromLyncCLient += ProviderChatMessageReceivedFromLyncClient;
                    provider.InstantMessageProviderExceptionOccured += ProviderInstantMessageProviderExceptionOccured;
                    provider.Notification += ProviderNotification;
                    provider.ChatClosed += ProviderChatClosed;
                    provider.ComposingStateChanged += ProviderComposingStateChanged;

                    //add provider to provider collection
                    m_Providers.Add(provider);
                }

                var presencesAvailableForChat = groupSubscription.Presences.Where(p => p.CanChat || isInInitialMode).ToList();

                var counter = 1;
                foreach (var presence in presencesAvailableForChat)
                {
                    if (counter > groupSubscription.HuntGroupNumber)
                        break;

                    var provider = GetProvider(session, presence.SipUri);

                    if (provider.Status == ChatRequestState.Idle)
                    {
                        provider.EstablishConnection(isInInitialMode);
                        
                        counter++;    
                    }
                }
            }
            catch (Exception exception)
            {
                Log.WarnFormat("Failed InitChatSession for session {0} and group '{1}' ERROR: {2}", session, groupSubscription.GroupId, exception.Message);
                throw;
            }
        }


        /// <summary>
        ///  From web group chat window to all lync clients
        /// </summary>
        /// <param name="groupPresence"></param>
        /// <param name="session"></param>
        /// <param name="message"></param>
        public void SendInstantMessageToLyncClient(string session, string message)
        {         
            var result = new List<UCInstantGroupMessageProvider>();

            try
            {
                var activeProvider = GetActiveProvider(session);

                if (activeProvider != null)
                {
                    result.Add(activeProvider);
                }
                else
                {
                    var providers = GetProviders(session).Where(p => p.Status == ChatRequestState.Created).ToList();

                    if ( providers.Count > 0)
                    {
                        result.AddRange(providers);    
                    }                    
                }                
            }
            catch (SessionNotFoundException)
            {
                Log.WarnFormat("SessionNotFoundException for session {0} and message '{1}'", session, message);
                throw;
            }

            result.ForEach(p => p.SendInstantMessage(message));
        }

        public void SendInstantMessageToLyncClient(string sipUri, string session, string message)
        {
            var provider = GetProvider(session, sipUri);

            if (provider != null)
            {
                provider.SendInstantMessage(message);
            }
        }

        /// <summary>
        /// Get all messages from the lync clients
        /// </summary>
        /// <param name="session"></param>
        /// <returns></returns>
        public List<GroupMessage> GetInstantMessagesFromLynClient(string session)
        {
            GroupMessageQueue cache;
            try
            {
                cache = Getcache(session);
            }
            catch (QueueNotFoundException cacheNotFoundException)
            {
                Log.WarnFormat("QueueNotFoundException for session {0}", session);
                throw;
            }

            return cache.GetAllAndDelete();
        }

        public List<GroupMessage> GetInstantMessagesFromLynClient(GroupSubscription groupSubscription, string session)
        {
            var messages = GetInstantMessagesFromLynClient(session);

            // set the display name based on the presence information
            foreach (var message in messages)
            {
                var presence = groupSubscription.Presences.Single(p => p.SipUri == message.SipUri);
                message.Name = presence.ContactName;
            }

            // hunt group logic.. only sent an instant message to ONE member of the group
            var instantMessages = messages.Where(m => m.Type == MessageType.InstantMessage).ToList();

            var activeProvider = GetActiveProvider(session);

            if (instantMessages.Count > 0)
            {
                if (activeProvider == null)
                {
                    // set active group chat presence
                    activeProvider = GetProviders(session).Single(p => p.SipUri == instantMessages.First().SipUri);
                    activeProvider.TakeOver = true;
                }

                // terminate all group presences but NOT the active group chat presence
                TerminateChat(groupSubscription, session);    
            }

            // filter only messages from active group chat member
            if (activeProvider != null)
            {
                foreach (var m in messages)
                {
                    if (m.SipUri != activeProvider.SipUri && m.Type == MessageType.InstantMessage)
                    {
                        messages.Remove(m);
                    }
                }                
            }

            return messages;
        }


        public StatusReport GetStatusReport()
        {
            return new StatusReport
            {
                NumberOfMessageQueues = m_MessageQueues.Count,
                TotalNumberOfMessages = m_MessageQueues.Sum(queue => queue.GetMessageCount()),
                NumberOfProviders = m_Providers.Count,
            };
        }

      
        public void TerminateChat(GroupSubscription groupSubscription, string session)
        {
            var activeProvider = GetActiveProvider(session);

            if (activeProvider != null)
            {
                var activeProviderContactName = groupSubscription.Presences.Single(p => p.SipUri == activeProvider.SipUri).ContactName;

                var providers = GetProviders(session).Where(p => p.Status == ChatRequestState.Created && p.SipUri != activeProvider.SipUri);

                foreach (var provider in providers)
                {                    
                    SendInstantMessageToLyncClient(provider.SipUri, session, "Group chat taken over by " + activeProviderContactName);
                    DisconnectProvider(provider);
                }
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ProviderComposingStateChanged(object sender, ComposingStateChangedEventArgs e)
        {
            var provider = (UCInstantGroupMessageProvider)sender;
            QueueMessage(provider.Session,
                         new GroupMessage(provider.SipUri, e.composingState.ToString(), MessageType.ComposingStateChanged));
        }

        
        private void DisconnectProvider(UCInstantGroupMessageProvider provider)
        {            
            provider.ChatRequestAcceptanceChanged -= ProviderChatRequestAcceptanceChanged;
            provider.ChatMessageReceivedFromLyncCLient -= ProviderChatMessageReceivedFromLyncClient;
            provider.InstantMessageProviderExceptionOccured -= ProviderInstantMessageProviderExceptionOccured;
            provider.Notification -= ProviderNotification;
            provider.ChatClosed -= ProviderChatClosed;
            provider.ComposingStateChanged -= ProviderComposingStateChanged;
            provider.Close();
        }
        
        private void DisposeProviders(string session)
        {
            var providers = GetProviders(session);

            foreach (var provider in providers)
            {
	            try
	            {
		            DisconnectProvider(provider);
		            provider.Dispose();
		            m_Providers.Remove(provider);
	            }
	            catch (Exception)
	            {
		            
	            }
            }
        }

        /// <summary>
        /// Occurs when the lync client has closed its chat window
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ProviderChatClosed(object sender, EventArgs e)
        {
            var provider = (UCInstantGroupMessageProvider)sender;

            var activeProvider = GetActiveProvider(provider.Session);

            if (activeProvider != null && provider.SipUri == activeProvider.SipUri )
            {
                QueueMessage(provider.Session, new GroupMessage(activeProvider.SipUri, "Sorry, this user is no longer available for chat. You can send an email or place a call instead.", MessageType.ChatClosed));
                //DisposeProviders(provider.Session);
            }
			DisposeProviders(provider.Session);
		}

        private UCInstantGroupMessageProvider GetActiveProvider(string session)
        {
            var provider = m_Providers.SingleOrDefault(p => p.Session == session && p.TakeOver);

            return provider;
        }

        /// <summary>
        /// Occurs when the provider sends a notification to pass to the end user
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ProviderNotification(object sender, NotificationEventArgs e)
        {
            var provider = (UCInstantGroupMessageProvider)sender;
            QueueMessage(provider.Session, new GroupMessage(provider.SipUri, e.message, MessageType.ServiceNotification));
        }

        /// <summary>
        /// Occurs when the provider notifies the router of an internal exception.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ProviderInstantMessageProviderExceptionOccured(object sender,
                                                                    InstantMessageProviderExceptionOccuredEventArgs e)
        {
            var provider = (UCInstantGroupMessageProvider)sender;
            QueueMessage(provider.Session, new GroupMessage(provider.SipUri, e.exception.Message, MessageType.ServiceError));
        }

        /// <summary>
        /// Occurs when the provider notifies the router that a chat message was received from the lync client
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ProviderChatMessageReceivedFromLyncClient(object sender, MessageReceivedFromLyncClientEventArgs e)
        {
            var provider = (UCInstantGroupMessageProvider)sender;
            QueueMessage(provider.Session, new GroupMessage(provider.SipUri, e.message, MessageType.InstantMessage));
        }

        /// <summary>
        /// Occurs when the provider notifies the router that a lync client has accepted a chat request
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ProviderChatRequestAcceptanceChanged(object sender, ChatRequestAcceptanceChangedEventArgs e)
        {
            var provider = (UCInstantGroupMessageProvider)sender;
            if (e.isAccepted)
            {
                QueueMessage(provider.Session, new GroupMessage(provider.SipUri, "Chat request accepted by the user! Go ahead and say something.", MessageType.ChatRequestAccepted));
			}
			else
			{
				//QueueMessage(provider.Session, new GroupMessage(provider.SipUri, "Sorry, this user is no longer available for chat. You can send an email or place a call instead.", MessageType.ChatRequestDenied));
				DisposeProviders(provider.Session);
			}
        }

        /// <summary>
        /// Adds a message to the queue
        /// </summary>
        /// <param name="session"></param>
        /// <param name="message"></param>
        private void QueueMessage(string session, GroupMessage message)
        {
            GroupMessageQueue cache;
            try
            {
                cache = Getcache(session);
            }
            catch (QueueNotFoundException cacheNotFoundException)
            {
                Log.WarnFormat("QueueNotFoundException for session {0} and message '{1}'", session, message);
                throw;
            }

            cache.Add(message);
        }

        private UCInstantGroupMessageProvider GetProvider(string session, string sipUri)
        {
            return m_Providers.SingleOrDefault(p => p.Session.Equals(session)
                                                    && p.SipUri.Equals(sipUri));
        }

        private IEnumerable<UCInstantGroupMessageProvider> GetProviders(string session, List<string> sipUris = null)
        {
            var providers = new List<UCInstantGroupMessageProvider>();

            if (sipUris != null)
            {
                sipUris.ForEach(sipUri =>
                {
                    var provider = GetProvider(session, sipUri);

                    if (provider != null)
                    {
                        providers.Add(provider);
                    }
                });
            }
            else
            {
                providers.AddRange(m_Providers.Where(p => p.Session == session));
            }

            return providers;
        }

        private GroupMessageQueue Getcache(string session)
        {
            var cache = m_MessageQueues.SingleOrDefault(p => p.Session.Equals(session));
            if (cache != null)
            {
                return cache;
            }
            throw new QueueNotFoundException();
        }


        private bool SessionProviderExists(string session, string sipUri)
        {
            var provider = m_Providers.SingleOrDefault(p => p.Session.Equals(session) && p.SipUri.Equals(sipUri));
            return provider != null;
        }

        private bool QueueExists(string session)
        {
            var cache = m_MessageQueues.SingleOrDefault(p => p.Session.Equals(session));
            return cache != null;
        }

        #endregion

        public void Dispose(string session)
        {            
            m_MessageQueues.Where(m => m.Session == session).ToList().Clear();
            m_Providers.Where(m => m.Session == session).ToList().Clear();
        }

      
    }
}